package msgcomm;

import java.util.HashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import common.Logger;

/**
 * Il Name Server si occupa di garantire l'univocita' dei nomi dei transceiver.
 * Questo avviene attraverso la registrazione del nome del transceiver presso il
 * Name Server.
 * La registrazione di un nome ha una scadenza, che deve pertanto essere periodicamente
 * rinnovata.
 */
public class NameServer {
	
	/**
	 * Disabilita il timer (per debug)
	 */
	protected boolean enable= true;
	/**
	 * lo stream su cui scrivere il log
	 */
	protected Logger log;
	
	/**
	 * Mappa da nome del transceiver al corrispondente timer
	 */
	private HashMap<String, LeaseTimer> nameLeases;
	/**
	 * Timer globale: gestisce tutti i timer
	 */
	private ScheduledThreadPoolExecutor globalTimer;
	
	/**
	 * Crea il Name Server
	 */
	public NameServer() {
		
		try {
			log= new Logger("NameServer.txt");
		} catch (Exception e) {
			System.err.println("Errore apertura file di log NameServer.txt");
			e.printStackTrace();
		}
		
		if (!enable) log.print("\nATTENZIONE: TIMER NON ABILITATO\n");
		
		nameLeases= new HashMap<String, NameServer.LeaseTimer>();
		
		// timer
		nameLeases= new HashMap<String, LeaseTimer>();
		globalTimer= new ScheduledThreadPoolExecutor(1);
		//globalTimer.setRemoveOnCancelPolicy(true);
	}
	
	/**
	 * Registra un nuovo nome
	 * @param name nome da registrare
	 * @return true se il nome e' stato registrato con successo, false se il nome e' gia' stato utilizzato
	 */
	public synchronized boolean addName(String name) {
		if (nameLeases.containsKey(name)) {
			return false;
		} else {
			// aggiunto il nome
			
			// timer
			final String fName= name;
			LeaseTimer lt= new LeaseTimer(fName, new Runnable() {
				@Override
				public void run() {
					if (!enable) return;
					log.print("Rilevato crash: timeout " + fName);
					//stop(true);
					
					delName(fName);
				}
			});
			nameLeases.put(name, lt);

			return true;
		}
	}
	
	/**
	 * Rimuove la registrazione di un nome
	 * @param name nome da rimuovere
	 */
	public synchronized void delName(String name) {
		
		// rimozione timer
		if (nameLeases.containsKey(name)) {
			LeaseTimer lt= nameLeases.get(name);
			lt.stop();
			nameLeases.remove(name);
		}
		
	}
	
	/**
	 * Rinnova la scadenza del timer di un nome
	 * @param name nome da rinfrescare
	 */
	public synchronized void refreshName(String name) {
		if (nameLeases.containsKey(name)) {
			LeaseTimer lt= nameLeases.get(name);
			lt.restart();
		}
	}
	
	/**
	 * Gestisce il timer relativo alla scadenza di un nome
	 */
	protected class LeaseTimer {
		/**
		 * Nome del timer
		 */
		private String name;
		/**
		 * Runnable da eseguire allo scadere del timer
		 */
		private Runnable run;

		/**
		 * Usato per cancellare il timer
		 */
		private ScheduledFuture<?> sf;
		
		/**
		 * Periodo di tutti i timer
		 */
		private final long period= 10; // in secondi
		
		/**
		 * Crea un nuovo LeaseTimer
		 * @param name nome del timer
		 * @param run runnable da eseguire allo scadere del timer
		 */
		public LeaseTimer(String name, Runnable run) {
			this.name= name;
			this.run= run;
			sf= globalTimer.scheduleAtFixedRate(run, period, period, TimeUnit.SECONDS);
		}
		
		/**
		 * Ferma l'esecuzione di questo timer
		 */
		public void stop() {
			stop(false);
		}
		
		/**
		 * Ferma l'esecuzione di questo timer
		 * @param silent se true non viene stampato niente su stderr se il timer non puo' essere fermato
		 */
		private void stop(boolean silent) {
			boolean b= sf.cancel(false);
			
			if (!b && !silent) {
				log.printErr("Timer \"" + name + "\": il timer non puo' essere fermato (restituito " + b + ")");
			}
		}
		
		/**
		 * Ferma il timer e fa ricominciare il conteggio del tempo
		 */
		public void restart() {
			//System.out.println("Refresh " + this);
			stop();
			sf= globalTimer.scheduleAtFixedRate(run, period, period, TimeUnit.SECONDS);
		}
		
		@Override
		public String toString() {
			return "timer \"" + name + "\"";
		}
	}
}
